#include <malloc.h>
#include <memory.h>
#include <stdlib.h>
#include "arrays.h"


/****************************************************************************
** FUNCTION PROTOTYPES
****************************************************************************/
static success_t match_movie_name(const movie_t * elem, const char * name);
static success_t match_game_name(const game_t * elem, const char * name);
static success_t match_playlist_name(const playlist_t * elem, const char * name);
static success_t match_player_name(const player_t * elem, const char * name);
static void init_game(game_t * elem);
static void fini_game(game_t * elem);


/****************************************************************************
** ABSTRACT ARRAY FUNCTIONS
****************************************************************************/
void arr_init(arr_t * self, int elemsize, init_t init, fini_t fini, 
			  match_t match)
{
	self->count = 0;
	self->capacity = 0;
	self->elemsize = elemsize;
	self->elements = NULL;
	self->init = init;
	self->fini = fini;
	self->match = match;
}

void arr_fini(arr_t * self)
{
	int i;
	
	if (self->fini != NULL)
	{
		for (i = 0; i < self->count; i++)
			self->fini((char*)self->elements + i * self->elemsize);
	}
	
	if (self->elements != NULL)
		free(self->elements);
	self->count = 0;
	self->elemsize = 0;
	self->elements = NULL;
	self->init = NULL;
	self->fini = NULL;
	self->match = NULL;
}

void * arr_append(arr_t * self)
{
	void * p;

	if (self->count > self->capacity)
	{
		/* UNREPORTABLE ERROR: sanity check */
		return FALSE;
	}
	if (self->count == self->capacity)
	{
		/* grow array */
		p = realloc(self->elements, 
			(self->capacity + ARRAY_CAPACITY_RESIZE) * self->elemsize);
		if (p == NULL)
		{
			mtm_error(err_OutOfMemory);
			return NULL;
		}
		self->capacity += ARRAY_CAPACITY_RESIZE;
		self->elements = p;
	}
	
	p = (char*)self->elements + self->count * self->elemsize;
	if (self->init != NULL)
		self->init(p);
	self->count += 1;
	return p;
}

success_t arr_remove(arr_t * self, int index)
{
	void * p = NULL;
	
	if (index < 0 || index >= self->count)
	{
		/* UNREPORTABLE ERROR: index out of range */
		return FALSE;
	}
	if (self->fini != NULL)
		self->fini((char*)self->elements + index * self->elemsize);
	if (index + 1 < self->count)
	{
		/* shift down elements */
		memmove((char*)self->elements + index * self->elemsize,
			(char*)self->elements + (index + 1) * self->elemsize,
			(self->count - index - 1) * self->elemsize);
	}
	self->count -= 1;
	if (self->capacity - ARRAY_CAPACITY_RESIZE > self->count)
	{
		/* shrink array */
		p = realloc(self->elements, 
			(self->capacity - ARRAY_CAPACITY_RESIZE) * self->elemsize);
		if (p == NULL)
		{
			mtm_error(err_OutOfMemory);
			return FALSE;
		}
		self->capacity -= ARRAY_CAPACITY_RESIZE;
		self->elements = p;
	}
	return TRUE;
}

int arr_find(const arr_t * self, const void * signature)
{
	int i;
	char * p = (char*)self->elements;
	
	if (self->match == NULL)
		return -1;

	for (i = 0; i < self->count; i++, p += self->elemsize)
	{
		if (self->match(p, signature))
			return i;
	}
	return -1;
}

void * arr_get(arr_t * self, const void * signature)
{
	int i;
	i = arr_find(self, signature);
	if (i != -1)
		return ((char*)self->elements) + (i * self->elemsize);
	return NULL;
}

void arr_sort(arr_t * self, compare_t cmp)
{
	if (self->elements == NULL)
		return;
	qsort(self->elements, self->count, self->elemsize, cmp);
}

void arr_getiter(const arr_t * self, iterator_t * iter)
{
	iter->index = 0;
	iter->arr = self;
}

void * arr_iternext(iterator_t * self)
{
	void * elem;
	if (self->index >= self->arr->count)
		return NULL;
	elem = (char*)self->arr->elements + self->index * self->arr->elemsize;
	self->index += 1;
	return elem;
}


/****************************************************************************
** CONCRETE ELEMENT HELPERS
****************************************************************************/
static success_t match_movie_name(const movie_t * elem, const char * name)
{
	if (strcmp(elem->name, name) == 0)
		return TRUE;
	return FALSE;
}

static success_t match_game_name(const game_t * elem, const char * name)
{
	if (strcmp(elem->name, name) == 0)
		return TRUE;
	return FALSE;
}

static success_t match_playlist_name(const playlist_t * elem, const char * name)
{
	if (strcmp(elem->name, name) == 0)
		return TRUE;
	return FALSE;
}

static success_t match_player_name(const player_t * elem, const char * name)
{
	if (strcmp(elem->name, name) == 0)
		return TRUE;
	return FALSE;
}

static void init_game(game_t * elem)
{
	arr_init_players(&elem->players);
}

static void fini_game(game_t * elem)
{
	arr_fini(&elem->players);
}


/****************************************************************************
** CONCRETE ELEMENT FUNCTIONS
****************************************************************************/
void arr_init_movies(arr_t * self)
{
	arr_init(self, sizeof(movie_t), NULL, NULL, (match_t)match_movie_name);
}

void arr_init_games(arr_t * self)
{
	arr_init(self, sizeof(game_t), (init_t)init_game, (fini_t)fini_game, 
		(match_t)match_game_name);
}

void arr_init_playlists(arr_t * self)
{
	arr_init(self, sizeof(playlist_t), NULL, NULL, 
		(match_t)match_playlist_name);
}

void arr_init_players(arr_t * self)
{
	arr_init(self, sizeof(player_t), NULL, NULL, (match_t)match_player_name);
}





























